import { BasicTransformEvent, Canvas, FabricImage, FabricObject, FabricObjectProps, Line, ModifiedEvent, ObjectEvents, Polygon, SerializedObjectProps, TPointerEvent, TPointerEventInfo, util, IText, XY, Rect, TEvent, TMat2D, Triangle, Point } from 'fabric'

interface IFabricDrawConfig {
  id: string,
  setRender: (a: any) => void,
  imageUrl?: string,
}

export enum DRAW_TYPE {
  polygon = 'polygon',
  rect = 'rect',
  circle = 'circle',
  line = 'line',
  triangle = 'triangle',
  ellipse = 'ellipse',
  path = 'path',
  text = 'text',
  image = 'image',
  default = 'default'
}

export enum CANVAS_TYPR {
  DEFAULT = 'DEFAULT',
  DRAW = 'DRAW'
}

export interface IFabricObject extends FabricObject<Partial<FabricObjectProps>, SerializedObjectProps, ObjectEvents> { }

type TTransformEvent = BasicTransformEvent<TPointerEvent> & {
  target: IFabricObject;
}

class FabricDraw {

  private canvas!: Canvas;
  tmpShape: IFabricObject | null = null;
  tmpType: string = DRAW_TYPE.polygon;
  canvasType: string = CANVAS_TYPR.DRAW
  static _ins: FabricDraw | null = null;
  render: any;
  private _downPoint: TPointerEventInfo | null = null;

  constructor(config: IFabricDrawConfig) {
    this.initCanvas(config)
    this.render = config.setRender
  }

  static initFabricDraw(config: IFabricDrawConfig) {
    if (!this._ins) {
      this._ins = new FabricDraw(config);
    }
    return this._ins;
  }

  public canvasInstance(): Canvas {
    return this.canvas
  }

  // 编辑类型
  public getCanvasType() {
    return this.canvasType
  }

  public isType(type: CANVAS_TYPR) {

    return this.getCanvasType() === type
  }

  changeCanvasType(type: CANVAS_TYPR, isRender: boolean = true) {
    this.canvasType = type
    switch (type) {
      case CANVAS_TYPR.DEFAULT:
        this.canvas.selection = true
        this.canvas.selectionColor = 'rgba(100, 100, 255, 0.3)'
        this.canvas.selectionBorderColor = 'rgba(255, 255, 255, 0.3)'
        this.canvas.skipTargetFind = false // 允许选中
        break
      case CANVAS_TYPR.DRAW:
        console.log('first changeCanvasType', type)

        this.canvas.selectionColor = 'transparent'
        // this.canvas.selectionBorderColor = 'transparent'
        this.canvas.selectionBorderColor = 'rgba(0, 0, 0, 0.2)'
        this.canvas.skipTargetFind = true // 禁止选中
        // this.canvas.selectionLineWidth = 4
        break
    }
    isRender && this.render({})
  }
  initCanvas(config: IFabricDrawConfig) {
    // this.changeType(DRAW_TYPE.rect)
    const canvas = new Canvas(config.id)
    canvas.setZoom(1)
    this.canvas = canvas
    // this.addImage(config.imageUrl)
    this.drawAxis();
    this.drawTicksAndLabels();
    this.changeCanvasType(CANVAS_TYPR.DRAW, false) // 设置初始值
    this.registerEvent()
  }

  // 修改绘制类型
  changeType(type: string) {
    this.tmpType = type
    this.render()
  }
  rerender() {
    this.canvas.requestRenderAll()
  }
  //
  registerEvent() {
    //
    this.canvas.on('mouse:down', (e) => this.canvasMouseDown(e, this))         // 鼠标在画布上按下
    this.canvas.on('mouse:move', (e) => this.canvasMouseMove(e, this))         // 鼠标在画布上移动
    this.canvas.on('mouse:up', (e) => this.canvasMouseUp(e, this))             // 鼠标在画布上抬起
    this.canvas.on('mouse:dblclick', (e) => this.canvasMouseDblclick(e, this)) // 鼠标在画布上双击
    this.canvas.on('object:modified', (e) => this.canvasObjectModified(e, this))
    // this.canvas.on('object:scaling', (e) => this.canvasObjectScaling(e, this))
    // this.canvas.on('selection:updated', (e) => this.canvasSelectionUpdated(e, this))
    document.addEventListener('keydown', (e) => this.keyboradEvent(e), false)
  }
  // 鼠标在画布上按下事件
  canvasMouseDown(e: TPointerEventInfo, that: FabricDraw) {
    console.log('canvasMouseDown', that.tmpType, this.canvasType)
    // 不允许绘制
    if (this.canvasType === CANVAS_TYPR.DEFAULT) {
      return
    }
    switch (that.tmpType) {
      case DRAW_TYPE.polygon:
        this.tmpShape ? this.changeCurrentPolygon(e) : that.createPolygon(e)
        break;
      case DRAW_TYPE.rect:
        this._downPoint = e
        break;
      default:
        alert('请选择绘制类型')
        break;
    }
  }
  canvasMouseMove(e: TPointerEventInfo, that: FabricDraw) {
    console.log('canvasMouseMove')
    if (!that.tmpShape) {
      return
    }
    switch (that.tmpType) {
      case DRAW_TYPE.polygon:
        this.changePolygonBelt(e)
        break;

      default:
        break;
    }
  }
  canvasMouseDblclick(e: TPointerEventInfo, that: FabricDraw) {
    console.log('canvasMouseDblclick')
    switch (that.tmpType) {
      case DRAW_TYPE.polygon:
        this.finishPolygon(e)
        break;

      default:
        break;
    }
  }

  canvasMouseUp(e: TPointerEventInfo, that: FabricDraw) {
    console.log('canvasMouseUp')
    switch (that.tmpType) {
      case DRAW_TYPE.rect:
        this.createRect(e)
        break;

      default:
        break;
    }
  }

  // 选中更新后触发，第一次为创建不会触发
  canvasSelectionUpdated(e: Partial<TEvent<TPointerEvent>> & {
    selected: FabricObject<Partial<FabricObjectProps>, SerializedObjectProps, ObjectEvents>[];
    deselected: FabricObject<Partial<FabricObjectProps>, SerializedObjectProps, ObjectEvents>[];
  }, that: FabricDraw) {
    console.log('canvasSelectionUpdated', e, e.selected)
  }

  canvasObjectModified(e: ModifiedEvent<TPointerEvent>, that: FabricDraw) {
    // 检测是否超出画布
    if (e.target) {
      this.disableCanvasMove(e.target)
    }
    // ! 这个函数会执行两次（原因还未找到），使用 e.e 暂时限制执行一次
    if (!e.e) return
    console.log('this.getMatrixTransformDirection(e.transform!)', this.getMatrixTransformDirection(e.transform!), e.transform?.originX, e.transform?.originY)
    const res = this.calcUpdatedPoints(e.target, e.transform!)
    console.log('canvasObjectModified res', res, e)
    let type = e.target.type
    this.canvas.remove(e.target)
    // 旋转时绘制会出错（比如：矩形），改为多边形绘制
    if (e.action == 'rotate') {
      type = DRAW_TYPE.polygon
    }
    switch (type) {
      case DRAW_TYPE.polygon:
        this.drawPolygon(res.points)
        break;
      case DRAW_TYPE.rect:
        /**
         * 如果是旋转，绘制矩形会有问题：
         * 旋转之后，left、top、width、height会发生改变矩形绘制不能直接绘制旋转的矩形，必须通过rotate设置旋转之后的矩形
         * 解决：
         * 1. 通过旋转之前的left、top、width、height，同时设置rotate角度。来绘制矩形
         * 2. 通过多边形绘制 将原来的矩形改为多边形绘制
         * （如果后端存储的是点位，可以考虑将其改为多边形绘制）
         */
        const rect = this.drawRect(res.down, res.up)
        rect?.set('points', res.points)
        break;
      default:
        break;
    }
  }

  canvasObjectScaling(e: BasicTransformEvent<TPointerEvent> & {
    target: FabricObject<Partial<FabricObjectProps>, SerializedObjectProps, ObjectEvents>;
  }, that: FabricDraw) {
    console.log('canvasObjectScaling', e,)
    switch (that.tmpType) {
      case DRAW_TYPE.rect:
        break;
      case DRAW_TYPE.polygon:
        const points = this.getTransformedPolygonPoints(e)
        console.log('canvasObjectScaling 11', points)
        break;
      default:
        break;
    }
  }


  keyboradEvent(e: KeyboardEvent) {
    const activeObject = this.canvas.getActiveObject()
    console.log('keyboradEvent', e, activeObject)
    if (activeObject) {
      switch (e.key) {
        case 'Escape':
          this.canvas.remove(activeObject)
          break;

        default:
          break;
      }
    }
  }

  // 添加背景图片到画布中
  async addImage(src?: string) {
    if (!src) {
      return
    }
    const res = await FabricImage.fromURL(src, {
      crossOrigin: 'anonymous',
    },
      {
        left: 0,
        top: 0,
        scaleX: 1,
        scaleY: 1,
        selectable: false,
        evented: false,
        lockMovementX: true,
        lockMovementY: true,
        lockScalingFlip: true,
        lockScalingX: true,
        lockScalingY: true,
        lockRotation: true,
        originX: 'left',
        originY: 'top',
      });
    // this.canvas.add(res)
    // 设置到最底层
    this.canvas.sendObjectToBack(res)
  }
  // 创建多边形
  createPolygon(e: TPointerEventInfo) {
    if (this.tmpType !== DRAW_TYPE.polygon) {
      return
    }
    const currentPoint = e.scenePoint
    this.tmpShape = new Polygon(
      [
        { x: currentPoint.x, y: currentPoint.y },
        { x: currentPoint.x, y: currentPoint.y }
      ],
      {
        fill: 'transparent',
        stroke: 'rgba(0, 0, 0, 0.2)',
        objectCaching: false
      }
    )
    this.canvas.add(this.tmpShape)
  }

  // 修改当前正在创建的多边形
  changeCurrentPolygon(e: TPointerEventInfo) {
    if (this.tmpType !== DRAW_TYPE.polygon) {
      return
    }
    const currentPoint = e.scenePoint

    const points = (this.tmpShape as Polygon)!.points

    points.push({
      x: currentPoint.x,
      y: currentPoint.y
    })
    this.rerender()
  }

  // 多边形橡皮带
  changePolygonBelt(e: TPointerEventInfo) {
    if (this.tmpType !== DRAW_TYPE.polygon) {
      return
    }
    const currentPoint = e.scenePoint
    let points = (this.tmpShape as Polygon)!.points

    points[points.length - 1].x = currentPoint.x
    points[points.length - 1].y = currentPoint.y

    this.rerender()

  }

  // 完成多边形绘制
  finishPolygon(e: TPointerEventInfo) {
    if (this.tmpType !== DRAW_TYPE.polygon) {
      return
    }
    const currentPoint = e.scenePoint
    let points = (this.tmpShape as Polygon)!.points
    points[points.length - 1].x = currentPoint.x
    points[points.length - 1].y = currentPoint.y

    points.pop()
    points.pop()
    this.canvas.remove(this.tmpShape!)
    this.tmpShape = this.drawPolygon(points)

    this.tmpShape = null
    this.rerender()
  }

  drawPolygon(points: XY[]) {
    if (!points.length) {
      return null
    }
    let polygon = new Polygon(points, {
      stroke: '#000',
      fill: 'transparent',
      strokeUniform: true,
      // lockRotation: true
    })
    // this.tmpShape = polygon
    this.canvas.add(polygon)
    return polygon
  }

  // 创建矩形
  createRect(e: TPointerEventInfo) {
    if (!this._downPoint) {
      return
    }
    const downPoint = this._downPoint!.scenePoint
    const upPoint = e.scenePoint
    console.log('createRect', downPoint, upPoint)

    // 创建矩形
    const rect = this.drawRect(downPoint, upPoint)
    const points = this.calcInitPoints(rect!)
    rect?.set('points', points)
    this._downPoint = null
  }

  drawRect(down: XY, up: XY): Rect | null {
    console.log('&&&&&&&&&&', down, up)
    // 点击事件，不生成矩形
    if (JSON.stringify(down) === JSON.stringify(up)) {
      return null
    }
    // 矩形参数计算
    let top = Math.min(down.y, up.y)
    let left = Math.min(down.x, up.x)
    let width = Math.abs(down.x - up.x)
    let height = Math.abs(down.y - up.y)

    // 矩形对象
    const rect = new Rect({
      top,
      left,
      width,
      height,
      fill: 'transparent',
      stroke: '#000'
    })

    console.log('rect', rect)
    // 将矩形添加到画布上
    this.canvas.add(rect)
    return rect
  }

  // 获取变换后的多边形点，但是转换后的值不太对
  getTransformedPolygonPoints(e: TTransformEvent) {

  }

  // 矩阵的方向
  getMatrixTransformDirection(transform: TTransformEvent['transform']) {
    const { originX, originY } = transform
    if (originX === 'left' && originY === 'top') {
      // 已左上角为原点缩放
      return true
    }
    // 以右下角为原点缩放
    return false;
  }

  // 绘制坐标轴线
  drawAxis() {
    const canvas = this.canvas
    // 设置坐标轴样式
    var axisOptions: Partial<FabricObjectProps> = {
      stroke: 'black',
      strokeWidth: 1,
      selectable: false,
    };

    // 绘制X轴
    var xAxis = new Line([0, 0, canvas.width, 0], axisOptions);
    canvas.add(xAxis);

    // 绘制Y轴（注意Y轴需要翻转，因为Fabric的坐标系原点在左上角）
    var yAxis = new Line([0, 0, 0, canvas.height], axisOptions);
    canvas.add(yAxis);
  }

  // 绘制刻度和标签（简化示例，仅演示概念）
  drawTicksAndLabels() {
    const canvas = this.canvas
    function addTickMarks(axisLength: number, interval: number, options: Partial<FabricObjectProps>) {
      for (let i = interval; i <= axisLength; i += interval) {
        let line = new Line([0, i, 3, i], options);
        let vLine = new Line([i, 0, i, 3], options);
        canvas.add(line);
        canvas.add(vLine);
        /// X轴标签（简化处理，实际应用可能需要更复杂的计算和对齐
        if (i < axisLength) {
          var xLabel = new IText(i.toString(), { left: i - 5, top: 0, fontSize: 10, selectable: false });
          canvas.add(xLabel);

          // Y轴标签（同上）
          var yLabel = new IText((i).toString(), { left: 0, top: i + 5, fontSize: 10, angle: -90, selectable: false });
          canvas.add(yLabel);
        }
      }
    }

    var tickOptions = { stroke: 'gray', strokeWidth: 1, selectable: false };
    addTickMarks(canvas.width, 20, tickOptions);
    addTickMarks(canvas.height, 20, tickOptions);

    // 添加坐标轴标签（X和Y）
    var xLabel = new IText('X', { left: canvas.width - 10, top: 0, fontSize: 14, fill: 'black', selectable: false });
    var yLabel = new IText('Y', { left: 0, top: canvas.height - 20, fontSize: 14, fill: 'black', selectable: false });
    canvas.add(xLabel);
    canvas.add(yLabel);
  }
  // 禁止拖拽或移动位置出画布
  disableCanvasMove(activeObject: FabricObject) {
    // activeObject.on('mouseup', ev => {
    //   if(!ev.target) return
    // 获取画布四个点的坐标
    const canvasBoundaries = this.canvas.calcViewportBoundaries()
    console.log('canvasBoundaries', canvasBoundaries)
    let objBoundingRect = activeObject.getBoundingRect()
    console.log('objBoundingRect', objBoundingRect)

    if (objBoundingRect.left < canvasBoundaries.tl.x) {
      activeObject.left = canvasBoundaries.tl.x
    }

    if (objBoundingRect.left + objBoundingRect.width > canvasBoundaries.br.x) {
      activeObject.left = canvasBoundaries.br.x - objBoundingRect.width
    }

    if (objBoundingRect.top < canvasBoundaries.tl.y) {
      activeObject.top = canvasBoundaries.tl.y
    }

    if (objBoundingRect.top + objBoundingRect.height > canvasBoundaries.br.y) {
      activeObject.top = canvasBoundaries.br.y - objBoundingRect.height
    }
    this.rerender()
    // })
  }

  // 获取画布上的矩形和多边形
  getObjects() {
    return this.canvas.getObjects().filter(i => {
      const types = ['rect', 'polygon']
      return types.includes(i.type)
    })
  }

  testObj() {
    // 创建一个矩形对象
    // const shape = new Rect({
    //   left: 50,
    //   top: 50,
    //   width: 100,
    //   height: 200,
    //   fill: 'red',
    //   originX: 'left',
    //   originY: 'top',
    // });

    const shape = new Polygon([
      {
        "x": 76,
        "y": 55.625
      },
      {
        "x": 113,
        "y": 34.625
      },
      {
        "x": 150,
        "y": 43.625
      },
      {
        "x": 110,
        "y": 87.625
      }
    ], {
      fill: 'red',
    });


    // 将矩形对象添加到画布中
    this.canvas.add(shape);
    this.initMatrix(shape)
    const points = this.calcInitPoints(shape)
    shape.set('points', points)
    console.log('***********************', shape, points)
    this.tmpShape = null
    this.tmpShape = shape
    this.tmpType = DRAW_TYPE.default
  }

  initMatrix(shape: FabricObject) {

    // 当前的变换矩
    console.log('calcTransformMatrix', shape.calcTransformMatrix())
    console.log('calcOwnMatrix', shape.calcOwnMatrix())
    console.log('qrDecompose', util.qrDecompose(shape.calcOwnMatrix()))

    console.log('transformMatrixKey', shape.transformMatrixKey())
    console.log('getObjectScaling', shape.getObjectScaling())
    console.log('getViewportTransform', shape.getViewportTransform())
    console.log('getCenterPoint', shape.getCenterPoint())
    console.log('getPointByOrigin left top', shape.getPointByOrigin('left', 'top'))
    console.log('getPointByOrigin right top', shape.getPointByOrigin('right', 'top'))
    console.log('rotate', shape.angle)
    console.log('getBoundingRect', shape.getBoundingRect())
    console.log('#######################')
    return
  }

  updateMatrix() {
    const shape = this.tmpShape!
    this.initMatrix(shape)

    // 测试使用
    // const points = this.calcUpdatedPoints(shape, { originX: 'left', originY: 'top', action: 'drag' })
    // 重新绘制图形
    // this.canvas.remove(this.tmpShape!)
    // this.drawPolygon(points)
    // this.rerender()
  }

  calcInitPoints(shape: IFabricObject): XY[] {
    if (!shape) {
      return []
    }
    // 兼容，如果shape中本身包含 points（比如：Polygon）直接返回
    if (shape.isType(DRAW_TYPE.polygon)) {
      return (shape as Polygon).points
    }
    const { left, top, width, height } = shape
    const points = []
    switch (shape.type) {
      case DRAW_TYPE.rect:
        points.push(...[
          { x: left, y: top },
          { x: left + width, y: top },
          { x: left + width, y: top + height },
          { x: left, y: top + height },
        ])
        break;
      case DRAW_TYPE.triangle:
        points.push(...[
          { x: left + width / 2, y: top },
          { x: left + width, y: top + height },
          { x: left, y: top + height },
        ])
      default:
        break;
    }
    return points
  }
  calcUpdatedPoints(shape: IFabricObject, transform: TTransformEvent['transform']): { points: XY[], down: XY, up: XY } {
    const newPoints: XY[] = []
    if (!Reflect.has(shape, 'points')) return { points: newPoints, down: { x: 0, y: 0 }, up: { x: 0, y: 0 } }
    const points = Reflect.get(shape, 'points')
    console.log('***********calcUpdatedPoints************')
    console.log('calcUpdatedPoints points, left top', shape, points, shape.left, shape.top)
    console.log('calcUpdatedPoints transform', transform)
    console.log('calcUpdatedPoints matrix', shape.calcOwnMatrix())
    console.log('calcUpdatedPoints getBoundingRect', shape.getBoundingRect())
    console.log('***********calcUpdatedPoints************')
    let n: XY[] = []
    switch (transform.action) {
      case 'drag':
        n = this.calcTransformPoints(shape, points)
        break;
      case 'scale':
      case 'scaleX':
      case 'scaleY':
        n = this.calcScaledPoints(shape, points, transform)
        break;
      case 'rotate':
        n = this.calcRotatedPoints(shape, points, transform)
        break;
      default:
        break;
    }
    console.log('重新计算之后的坐标', n)
    // 描边测试结果是否重合
    // this.drawPolygon(n)
    return { points: n, down: shape.getPointByOrigin('left', 'top'), up: shape.getPointByOrigin('right', 'bottom') }
  }

  // 计算缩放后点的坐标
  calcScaledPoints(shape: IFabricObject, points: XY[], transform: TTransformEvent['transform']) {
    const matrix: TMat2D = shape.calcTransformMatrix()

    // const direction = this.getMatrixTransformDirection(transform)

    // let originMatrix: TMat2D = [1, 0, 0, 1, -shape.left, -shape.top] // 修改原点矩阵

    // if (!direction) {
    const { originX, originY } = transform
    const originPoint = shape.getPointByOrigin(originX, originY)
    console.log('leftTopPoint', originPoint)
    let originMatrix: TMat2D = [1, 0, 0, 1, -originPoint.x, -originPoint.y] // 修改原点矩阵
    // originMatrix = util.multiplyTransformMatrices(originMatrix, [1, 0, 0, 1, leftTopPoint.x, leftTopPoint.y])
    // }

    const n = points.map((p: XY) => {
      // 修改原点为左上角
      const transformP = this.transformPoint(p, originMatrix)
      // 按照缩放矩阵进行缩放各个点
      const scaleP = this.transformPoint(transformP, matrix, true)
      console.log('transformP originMatrix', transformP, originMatrix, scaleP)
      // 最后将 偏移位置在加回去
      return this.transformPoint(scaleP, util.invertTransform(originMatrix))
      // return this.transformPoint(p, util.multiplyTransformMatrixArray([originMatrix, matrix]))
    })
    return n
  }

  // 计算拖拽后点的坐标
  calcTransformPoints(shape: IFabricObject, points: XY[]) {
    let left = Infinity, top = Infinity
    points.forEach((p: XY) => {
      left = Math.min(left, p.x)
      top = Math.min(top, p.y)
    })
    return points.map((p: XY) => {
      // 移动到原点O位置
      const originP = this.transformPoint(p, [1, 0, 0, 1, -left, -top])
      return this.transformPoint(originP, [1, 0, 0, 1, shape.left, shape.top])
    })
  }

  calcRotatedPoints(shape: IFabricObject, points: XY[], transform: TTransformEvent['transform']) {
    const matrix = shape.calcTransformMatrix()
    const { x, y } = shape.getPointByOrigin(transform.originX, transform.originY)
    console.log('中心点坐标：', x, y)
    // 获取对象的旋转角度
    // const angle = shape.angle;
    // const cos = Math.cos(angle * Math.PI / 180);
    // const sin = Math.sin(angle * Math.PI / 180);
    // 利用选择角度，计算旋转矩阵
    // const rotateM : TMat2D = [cos, sin, -sin, cos, 0, 0]

    // 还原矩阵
    const originM: TMat2D = [1, 0, 0, 1, -x, -y]
    // console.log('旋转矩阵：', rotateM)
    const n = points.map((p: XY) => {
      // 移动到原点O位置
      const originP = this.transformPoint(p, originM)
      const rotateP = this.transformPoint(originP, matrix, true)
      return this.transformPoint(rotateP, util.invertTransform(originM))
    })
    console.log("旋转后的点坐标：", n);
    return n
  }

  transformPoint(p: XY, t: TMat2D, ignoreOffset?: boolean): Point {
    return new Point(p).transform(t, ignoreOffset)
  };
}

export default FabricDraw
